//*************************************************************************************
// File:     TimeSortedEventQueue.cs
//*************************************************************************************
// Description: Define una clase que encapsula una cola de eventos ordenada por tiempo
//              y la clase que implementa la interfaz IComparer, y que utilizaremos en
//              la reordenacion de la cola de eventos.
//*************************************************************************************
// Classes:      EventTimeComparer : IComparer
//              TimeSortedEventQueue : IDisposable
//*************************************************************************************
// Use:         EventDispatcher utiliza TimeSortedEventQueue para mantener una cola de
//              eventos ordenados en el tiempo.
//              Por otra parte, TimeSortedEventQueue contiene una instancia de la clase
//              QuickSorter, que usa una instancia de EventTimeComparer como parametro
//              comparador en el constructor.
//*************************************************************************************
// Author:      ManOguaR
//*************************************************************************************

using Xeralabs.Core.Events;
using Xeralabs.Core.Utils.Internals;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

namespace Xeralabs.Core.Internals
{
    /// <summary>
    /// Comparador de tiempo de eventos XGDESKEvent.
    /// </summary>
    public class EventTimeComparer : IComparer
    {
        #region Miembros de IComparer
        /// <summary>
        /// Compara dos objetos que deben ser XGDESKEvent.
        /// </summary>
        /// <param name="x">Un objeto que debe ser XGDESKEvent.</param>
        /// <param name="y">Un objeto que debe ser XGDESKEvent.</param>
        /// <returns>0 - Ambos objetos son iguales o nulos, o no son XGDESKEvent; 1 - x mayor que y o y es nulo; 2 - y mayor que x o x es nulo.</returns>
        public int Compare(object x, object y)
        {
            if (x == null && y == null)
                return 0;
            else if (x == null && y != null)
                return 1;
            else if (x != null && y == null)
                return -1;

            if (((XGDESKEvent)x).TimeMark < ((XGDESKEvent)y).TimeMark)
                return 1;
            else if (((XGDESKEvent)x).TimeMark > ((XGDESKEvent)y).TimeMark)
                return -1;
            else
                return 0;
        }
        #endregion
    }

    /// <summary>
    /// Clase que encapsula una cola de eventos ordenada en el tiempo y metodos de administracion.
    /// </summary>
    public class EventQueue : IDisposable
    {
        private List<XGDESKEvent> _queueList = null;
        private QuickSorter _sorter = null;
        private bool _sorting;
        private bool _dequeue;
        private AutoResetEvent _waitHandle;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="initialCapacity">La capacidad inicial de la cola de eventos antes de redimensionarla.</param>
        public EventQueue(int initialCapacity)
        {
            this._queueList = new List<XGDESKEvent>(initialCapacity);
            this._sorter = new QuickSorter(new EventTimeComparer(), new DefaultSwap());

            this._waitHandle = new AutoResetEvent(false);
        }

        /// <summary>
        /// Obtiene el tiempo de lanzamiento del siguiente evento.
        /// </summary>
        /// <returns>Un valor con el tiempo de lanzamiento del siguiente evento; si es -1.0 no quedan eventos pendientes.</returns>
        public double PeekTime()
        {
            if (_queueList.Count > 0)
                return _queueList[_queueList.Count - 1].TimeMark;

            return -1.0;
        }
        /// <summary>
        /// Inserta un evento en la cola, ordenado segun el tiempo de lanzamiento.
        /// </summary>
        /// <param name="evt">El evento a insertar.</param>
        public void EnqueueEvent(XGDESKEvent evt)
        {
            if (evt == null)
                throw new ArgumentNullException("evt");
            
            if (_dequeue)
                _waitHandle.WaitOne(-1);

            _sorting = true;
            lock (_queueList)
            {
                if (_queueList.Count == 0)
                {
                    _queueList.Add(evt);
                    return;
                }
                else if (_queueList.Count == 1)
                {
                    _queueList.Add(evt);
                    _sorter.Sort(_queueList);
                    return;
                }
                else
                {
                    if (_queueList[0].TimeMark <= evt.TimeMark)
                    {
                        _queueList.Insert(0, evt);
                        return;
                    }
                    if (_queueList[_queueList.Count - 1].TimeMark > evt.TimeMark)
                    {
                        _queueList.Add(evt);
                        return;
                    }

                    int low = _queueList.Count - 2;
                    int up = _queueList.Count - 1;

                    if (_queueList[low].TimeMark > evt.TimeMark)
                    {
                        _queueList.Add(evt);
                    }
                    else if (_queueList.Count < 10)
                    {
                        _queueList.Add(evt);
                        _sorter.Sort(_queueList);
                        return;
                    }
                    else
                    {
                        low = 0;
                        up = _queueList.Count - 1;
                        int groupSize = _queueList.Count / 2;

                        while (groupSize > 10)
                        {
                            if ((_queueList[low].TimeMark >= evt.TimeMark) && (_queueList[low + groupSize].TimeMark < evt.TimeMark))
                            {
                                up = groupSize;
                            }
                            else if ((_queueList[low + groupSize].TimeMark >= evt.TimeMark) && (_queueList[up].TimeMark < evt.TimeMark))
                            {
                                low = groupSize;
                            }
                            groupSize /= 2;
                        }
                        _queueList.Insert(up - 1, evt);
                    }

                    _sorter.Sort(_queueList, low, up - 1);
                }
            }
            _waitHandle.Set();
            _sorting = false;
        }
        /// <summary>
        /// Retira el siguiente evento en la cola, ordenado segun el tiempo de lanzamiento.
        /// </summary>
        /// <returns>El siguiente evento en la cola.</returns>
        public XGDESKEvent DequeueEvent()
        {
            if (_queueList.Count > 0)
            {
                if (_sorting)
                    _waitHandle.WaitOne(-1);

                _dequeue = true;
                XGDESKEvent res;
                lock (_queueList)
                {
                    res = _queueList[_queueList.Count - 1];
                    _queueList.RemoveAt(_queueList.Count - 1);
                }
                _waitHandle.Set();
                _dequeue = false;
                return res;
            }
            throw new InvalidOperationException("No events to dequeue");
        }

        /// <summary>
        /// Retira todos los eventos con entidad emisora identificada por el parametro <see cref="entityGUID"/>.
        /// </summary>
        /// <param name="entityGUID">El identificador unico de 64 bits relacionado con la entidad emisora.</param>
        /// <returns>Una lista de eventos con emisor <see cref="entityGUID"/>.</returns>
        public List<XGDESKEvent> RemoveEvents(ulong entityGUID)
        {
            List<XGDESKEvent> res = null;
            lock (this._queueList)
            {
                for (int a = 0; a < this._queueList.Count; a++)
                {
                    if (this._queueList[a].Sender == entityGUID)
                    {
                        if (res == null)
                            res = new List<XGDESKEvent>();

                        res.Add(this._queueList[a]);
                        this._queueList.RemoveAt(a);
                    }
                }
                _sorter.Sort(_queueList);
            }
            return res;
        }

        #region Miembros de IDisposable
        /// <summary>
        /// Libera todos los recursos utilizados por la instancia.
        /// </summary>
        public void Dispose()
        {
            if (_queueList.Count > 0)
                throw new InvalidOperationException();

            _queueList = null;
            _sorter = null;
        }
        #endregion

    }
}